package com.lkdont.android.media.opengl

import android.graphics.SurfaceTexture
import android.opengl.*
import android.os.Build
import android.util.Log
import android.view.Surface

/**
 * EGL是OpenGL ES渲染API与本地窗口系统之间的一个中间接口层。
 * EGL 的使用要遵循一些固定的步骤，按照这些步骤去配置、创建、渲染、释放。
 * 1、创建与本地窗口系统的连接，调用 eglGetDisplay 方法得到 EGLDisplay
 * 2、初始化 EGL 方法，调用 eglInitialize 方法初始化
 * 3、确定渲染表面的配置信息，调用 eglChooseConfig 方法得到 EGLConfig
 * 4、创建渲染上下文，通过 EGLDisplay 和 EGLConfig ，调用 eglCreateContext 方法创建渲染上下文，得到 EGLContext
 * 5、创建渲染表面，通过 EGLDisplay 和 EGLConfig ，调用 eglCreateWindowSurface 方法创建渲染表面，得到 EGLSurface
 * 6、绑定上下文，通过 eglMakeCurrent 方法将 EGLSurface、EGLContext、EGLDisplay 三者绑定，接下来就可以使用 OpenGL 进行绘制了。
 * 7、交换缓冲，当用 OpenGL 绘制结束后，使用 eglSwapBuffers 方法交换前后缓冲，将绘制内容显示到屏幕上
 * 8、释放 EGL 环境，绘制结束，不再需要使用 EGL 时，取消 eglMakeCurrent 的绑定，销毁  EGLDisplay、EGLSurface、EGLContext。
 *
 * @author lkdont
 */
class EGLCore @JvmOverloads constructor(
    sharedContext: EGLContext = EGL14.EGL_NO_CONTEXT,
    flags: Int = 0
) {
    companion object {

        private const val TAG = "EGLCore"

        /*** 允许录制  */
        const val FLAG_RECORDABLE = 0x01

        /*** 尝试使用GLES3  */
        const val FLAG_TRY_GLES_3 = 0x02

        /*** Android扩展：是否允许录制  */
        private var EGL_RECORDABLE_ANDROID = 0

        init {
            EGL_RECORDABLE_ANDROID = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                EGLExt.EGL_RECORDABLE_ANDROID
            } else {
                0x3142
            }
        }
    }

    /*** EGL版本 */
    var eglVersion = -1
        private set

    /*** EGL配置 */
    var eglConfig: EGLConfig? = null
        private set

    /*** EGL上下文 */
    var eglContext = EGL14.EGL_NO_CONTEXT
        private set

    /*** EGL显示接口 */
    var eglDisplay = EGL14.EGL_NO_DISPLAY
        private set

    init {
        // 1、创建与本地窗口系统的连接，默认获取显示设备是Android设备的屏幕
        eglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY)
        if (eglDisplay === EGL14.EGL_NO_DISPLAY) {
            // 获取显示设备失败
            throw RuntimeException("unable to get EGL14 display")
        }
        // 2、初始化 EGL
        val version = IntArray(2)
        if (!EGL14.eglInitialize(eglDisplay, version, 0, version, 1)) {
            // 初始化EGL失败
            eglDisplay = null
            throw RuntimeException("unable to initialize EGL14")
        }
        // 判断是否使用GLES3
        if (flags and FLAG_TRY_GLES_3 != 0) {
            // 尝试调用GLES3
            val config = getEGLConfig(flags, 3)
            if (config != null) {
                val attrList = intArrayOf(
                    EGL14.EGL_CONTEXT_CLIENT_VERSION, 3,
                    EGL14.EGL_NONE
                )
                val context = EGL14.eglCreateContext(
                    eglDisplay, config, sharedContext,
                    attrList, 0
                )
                if (EGL14.eglGetError() == EGL14.EGL_SUCCESS) {
                    // 获取GLES3成功
                    eglConfig = config
                    eglContext = context
                    eglVersion = 3
                }
            }
        }
        // 判断是否应该使用GLES2
        if (eglContext === EGL14.EGL_NO_CONTEXT) {
            // 调用GLES2
            val config = getEGLConfig(flags, 2)
            if (config != null) {
                val attrList = intArrayOf(
                    EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
                    EGL14.EGL_NONE
                )
                val context = EGL14.eglCreateContext(
                    eglDisplay, config, sharedContext,
                    attrList, 0
                )
                if (EGL14.eglGetError() == EGL14.EGL_SUCCESS) {
                    // 获取GLES2成功
                    eglConfig = config
                    eglContext = context
                    eglVersion = 2
                }
            }
        }
        // 通过query确认当前的版本
        val values = IntArray(1)
        EGL14.eglQueryContext(
            eglDisplay, eglContext, EGL14.EGL_CONTEXT_CLIENT_VERSION,
            values, 0
        )
        Log.d(
            TAG,
            "EGLCore: current egl context client version is " + values[0]
        )
    }

    /**
     * 获取合适的EGLConfig
     *
     * @param flags   参数
     * @param version GLES版本
     * @return EGLConfig
     */
    private fun getEGLConfig(flags: Int, version: Int): EGLConfig? {
        var renderableType = EGL14.EGL_OPENGL_ES2_BIT
        if (version >= 3) {
            renderableType = renderableType or EGLExt.EGL_OPENGL_ES3_BIT_KHR
        }
        val attrList = intArrayOf(
            EGL14.EGL_RED_SIZE, 8,
            EGL14.EGL_GREEN_SIZE, 8,
            EGL14.EGL_BLUE_SIZE, 8,
            EGL14.EGL_ALPHA_SIZE, 8,  // EGL14.EGL_DEPTH_SIZE, 16,
            // EGL14.EGL_STENCIL_SIZE, 8,
            EGL14.EGL_RENDERABLE_TYPE, renderableType,
            EGL14.EGL_NONE, 0,  // 占位
            EGL14.EGL_NONE
        )
        if (flags and FLAG_RECORDABLE != 0) {
            // 允许录制
            attrList[attrList.size - 3] = EGL_RECORDABLE_ANDROID
            attrList[attrList.size - 2] = 1
        }
        val configs =
            arrayOfNulls<EGLConfig>(1)
        val numConfigs = IntArray(1)
        // 3、确定渲染表面的配置信息
        if (!EGL14.eglChooseConfig(
                eglDisplay,
                attrList, 0,
                configs, 0, configs.size,
                numConfigs, 0
            )
        ) {
            Log.w(
                TAG,
                "unable to find RGB8888 / $version EGLConfig"
            )
            return null
        }
        return configs[0]
    }

    /**
     * Creates an EGL surface associated with a Surface.
     * If this is destined for MediaCodec, the EGLConfig should have the "recordable" attribute.
     */
    fun createWindowSurface(surface: Any): EGLSurface {
        if (surface !is Surface && surface !is SurfaceTexture) {
            throw GLException(0, "invalid surface: $surface")
        }
        // Create a window surface, and attach it to the Surface we received.
        val surfaceAttrs = intArrayOf(
            EGL14.EGL_NONE
        )
        val eglSurface = EGL14.eglCreateWindowSurface(
            eglDisplay, eglConfig,
            surface, surfaceAttrs, 0
        )
        Utils.checkGLError("eglCreateWindowSurface")
        if (eglSurface == null) {
            throw GLException(0, "surface was null")
        }
        return eglSurface
    }

    /**
     * Creates an EGL surface associated with an offscreen buffer.
     */
    fun createOffscreenSurface(width: Int, height: Int): EGLSurface? {
        val surfaceAttrs = intArrayOf(
            EGL14.EGL_WIDTH, width,
            EGL14.EGL_HEIGHT, height,
            EGL14.EGL_NONE
        )
        val eglSurface = EGL14.eglCreatePbufferSurface(
            eglDisplay, eglConfig,
            surfaceAttrs, 0
        )
        Utils.checkGLError("eglCreatePbufferSurface")
        if (eglSurface == null) {
            throw java.lang.RuntimeException("surface was null")
        }
        return eglSurface
    }

    /**
     * Performs a simple surface query.
     */
    fun querySurface(eglSurface: EGLSurface?, what: Int): Int {
        val value = IntArray(1)
        EGL14.eglQuerySurface(eglDisplay, eglSurface, what, value, 0)
        return value[0]
    }

    /**
     * Destroys the specified surface.  Note the EGLSurface won't actually be destroyed if it's
     * still current in a context.
     */
    fun releaseSurface(eglSurface: EGLSurface?) {
        EGL14.eglDestroySurface(eglDisplay, eglSurface)
    }

    /**
     * Makes our EGL context current, using the supplied surface for both "draw" and "read".
     */
    fun makeCurrent(eglSurface: EGLSurface?) {
        if (eglDisplay === EGL14.EGL_NO_DISPLAY) {
            // called makeCurrent() before create?
            Log.d(
                TAG,
                "NOTE: makeCurrent w/o display"
            )
        }
        if (!EGL14.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)) {
            throw java.lang.RuntimeException("eglMakeCurrent failed")
        }
    }

    /**
     * Makes our EGL context current, using the supplied "draw" and "read" surfaces.
     */
    fun makeCurrent(
        drawSurface: EGLSurface?,
        readSurface: EGLSurface?
    ) {
        if (eglDisplay === EGL14.EGL_NO_DISPLAY) {
            // called makeCurrent() before create?
            Log.d(
                TAG,
                "NOTE: makeCurrent w/o display"
            )
        }
        if (!EGL14.eglMakeCurrent(eglDisplay, drawSurface, readSurface, eglContext)) {
            throw java.lang.RuntimeException("eglMakeCurrent(draw,read) failed")
        }
    }

    /**
     * Calls eglSwapBuffers.  Use this to "publish" the current frame.
     *
     * @return false on failure
     */
    fun swapBuffers(eglSurface: EGLSurface?): Boolean {
        return EGL14.eglSwapBuffers(eglDisplay, eglSurface)
    }

    /**
     * Sends the presentation time stamp to EGL.  Time is expressed in nanoseconds.
     */
    fun setPresentationTime(
        eglSurface: EGLSurface?,
        nsecs: Long
    ) {
        EGLExt.eglPresentationTimeANDROID(eglDisplay, eglSurface, nsecs)
    }

    /**
     * Returns true if our context and the specified surface are current.
     */
    fun isCurrent(eglSurface: EGLSurface): Boolean {
        return eglContext == EGL14.eglGetCurrentContext()
                && eglSurface == EGL14.eglGetCurrentSurface(EGL14.EGL_DRAW)
    }

    /**
     * 释放资源
     *
     *
     * 必须在创建此对象的同一个线程调用此方法释放资源
     */
    fun release() {
        if (eglDisplay !== EGL14.EGL_NO_DISPLAY) {
            // Android is unusual in that it uses a reference-counted EGLDisplay.  So for
            // every eglInitialize() we need an eglTerminate().
            EGL14.eglMakeCurrent(
                eglDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE,
                EGL14.EGL_NO_CONTEXT
            )
            EGL14.eglDestroyContext(eglDisplay, eglContext)
            EGL14.eglReleaseThread()
            EGL14.eglTerminate(eglDisplay)
        }
        eglDisplay = EGL14.EGL_NO_DISPLAY
        eglContext = EGL14.EGL_NO_CONTEXT
        eglConfig = null
        eglVersion = -1
    }

    @Throws(Throwable::class)
    protected fun finalize() {
        if (eglDisplay !== EGL14.EGL_NO_DISPLAY) {
            // We're limited here -- finalizers don't run on the thread that holds
            // the EGL state, so if a surface or context is still current on another
            // thread we can't fully release it here.  Exceptions thrown from here
            // are quietly discarded.  Complain in the log file.
            Log.w(
                TAG,
                "WARNING: EglCore was not explicitly released -- state may be leaked"
            )
            release()
        }
    }

}